package top.codedance.iotp.client;

import cd.io.netty.channel.Channel;
import top.codedance.iotp.client.net.HttpServer;
import top.codedance.iotp.client.net.Httpd;
import top.codedance.iotp.client.net.configuration.CliCmdOptionsConfig;
import top.codedance.iotp.client.net.configuration.ExecuterRegistConfig;
import top.codedance.iotp.client.net.Client;
import top.codedance.iotp.client.net.manage.ManageService;
import top.codedance.iotp.common.entity.CommandTeam;
import top.codedance.iotp.common.entity.ExecuterRegistEntity;
import top.codedance.iotp.common.entity.NetworkEntity;
import top.codedance.iotp.common.executer.CommonClientPluginExecuter;
import top.codedance.iotp.common.util.CommandCli;
import top.codedance.iotp.common.util.OSinfo;
import top.codedance.iotp.common.util.AsymmetricEncryption;
import top.codedance.iotp.common.util.SystemUtil;
import top.codedance.iotp.loadblance.IotClientLoadBlance;
import top.codedance.iotp.loadblance.common.AlgorithmType;
import top.codedance.iotp.loadblance.common.CAlgorithmType;
import top.codedance.iotp.loadblance.entiy.RegistyUri;
import org.apache.log4j.Level;
import org.apache.log4j.Logger;
import org.ini4j.Profile;
import org.ini4j.Wini;

import java.io.File;
import java.io.IOException;
import java.util.*;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

public class ClientApplication {

    public static final Long VERSION = Long.valueOf(Version.VERSION);
    public static CommandCli commandCli; //命令解析工具
    public static String DEVICE_ID = "testDeviceId"; //设备唯一编号
    public static volatile AsymmetricEncryption.KeyStringPair keyStringPair; //通讯密钥对
    public static volatile String serverPublicKey; //服务端公钥
    public static Wini ini = null; //ini文件读取器
    public static Map<CommandTeam, CommonClientPluginExecuter> syncExcuters = new HashMap<>(); //同步执行器插件集合
    public static Map<CommandTeam, CommonClientPluginExecuter> excuters = new HashMap<>(); //异步执行器插件集合
    public static ExecutorService pool; //线程池管理
    public static ExecutorService asyncWorkerPool = Executors.newCachedThreadPool();
    public static ExecutorService syncWorkerPool = Executors.newCachedThreadPool();
    public static Channel channel = null; //通讯通道
    public static Timer timer = new Timer();
    public static volatile long dataVersion = 0; //数据续列
    public static boolean enableLoadBlance = false; //是否开启分布式，如开启将使用注册中心来分配注册到哪个server，手动指定server IP 无效
    public static int lbType = 0; //负载均衡类型
    public static String[] registryHosts = new String[]{"http://127.0.0.1:10014"}; //注册中心的地址
    private static int httpdPort = 0;
    public static int ioThreads = Runtime.getRuntime().availableProcessors() * 2;
    public static int workerThreads = ioThreads * 4;
    public static int httpApiServerPort = 0;
    public static String host = "";
    public static int port = 0;

    private static Logger logger;

    public static void main(String[] args) {
        System.setProperty("java.net.preferIPv4Stack", "true");
        System.setProperty("io.netty.noPreferDirect", "true");
        System.setProperty("io.netty.tmpdir", OSinfo.isWindows() ? System.getProperty("java.io.tmpdir") : "/var/tmp");
        commandCli = new CommandCli(args, CliCmdOptionsConfig.getCmds());
        logger = getLogger(ClientApplication.class);
        logger.setLevel(Level.toLevel(ClientApplication.commandCli.getArg("logLevel", "ERROR")));
        initIniConfig();

        for (ExecuterRegistEntity executerRegistEntity : ExecuterRegistConfig.getSyscExecuters()) {
            registerSyncExecuter(executerRegistEntity.getCommandTeam(), executerRegistEntity.getCommonExecuter());
        }

        for (ExecuterRegistEntity executerRegistEntity : ExecuterRegistConfig.getExecuters()) {
            registerExecuter(executerRegistEntity.getCommandTeam(), executerRegistEntity.getCommonExecuter());
        }

        DEVICE_ID = commandCli.getArg("deviceId", "testDeviceId");
        enableLoadBlance = commandCli.getArg("enableLoadBlance", false);
        lbType = commandCli.getArg("lbType", 0);
        registryHosts = commandCli.getArg("registryHosts",  registryHosts[0]).split("\\|");
        host = commandCli.getArg("host", "127.0.0.1");
        port = commandCli.getArg("port", 0);
        httpdPort = commandCli.getArg("httpdPort", 0);
        httpApiServerPort = commandCli.getArg("httpApiServerPort", 0);
        ioThreads = commandCli.getArg("ioThreads", Runtime.getRuntime().availableProcessors() * 2);
        workerThreads = commandCli.getArg("workerThreads", ioThreads * 4);
        intPool();

        run();
    }

    private static void intPool(){
        int executorCount = 1;
        if(!host.trim().isEmpty() && port > 0){
            executorCount++;
        }
        if(httpdPort > 0){
            executorCount++;
        }
        if(httpApiServerPort > 0){
            executorCount++;
        }
        pool = Executors.newFixedThreadPool(executorCount);
    }

    public static void run() {
        pool.submit(new ManageService(System.in, System.out));

        if(!host.trim().isEmpty() && port > 0) {
            pool.submit(() -> {
                while (true) {
                    try {
                        new Client(host, port).run();
                    } catch (Exception ex) {
                        logger.debug(ex.getMessage());
                        ClientApplication.syslogInfo("Client connect fail!");
                    }
                    try {
                        Thread.sleep(2000);
                        ClientApplication.syslogInfo("Client reconnecting ... ");
                    } catch (Exception ex) {
                    }
                }
            });
        }

        if(httpdPort > 0) {
            pool.submit(new Httpd(commandCli.getArg("httpdHost", "0.0.0.0"), httpdPort));
        }

        if(httpApiServerPort > 0){
            pool.submit(new HttpServer(httpApiServerPort));
        }

        try {
            pool.awaitTermination(10, TimeUnit.SECONDS);
        } catch (InterruptedException e) {
            logger.info("thread pool finished, and restart task.");
        } finally {
            if (pool.isShutdown()) {
                intPool();
                try {
                    Thread.sleep(2000);
                    ClientApplication.syslogInfo("Client restarting ... ");
                } catch (Exception ex) {
                }
                run();
            }
        }
    }

    /**
     * 注册同步执行器
     *
     * @param commandTeam
     * @param commonExecuter
     */
    private static void registerSyncExecuter(CommandTeam commandTeam, CommonClientPluginExecuter commonExecuter) {
        syncExcuters.put(commandTeam, commonExecuter);
    }

    /**
     * 注册异步执行器
     *
     * @param commandTeam
     * @param commonExecuter
     */
    private static void registerExecuter(CommandTeam commandTeam, CommonClientPluginExecuter commonExecuter) {
        excuters.put(commandTeam, commonExecuter);
    }

    /**
     * 获取jar包所在路径
     * @return
     */
    public static String getJarDirPath(){
        return SystemUtil.getRuntimeDirPath(ClientApplication.class);
    }

    /**
     * 读取ini配置文件
     */
    public static void initIniConfig() {
        String runtimeDirPath = getJarDirPath();
        String iniFilePath = runtimeDirPath + "init.ini";
        File f = new File(iniFilePath);
        if (f.exists()) {
            try {
                ini = new Wini(f);
            } catch (IOException e) {
                ClientApplication.syslogInfo("Ini file configure fail.");
            }
        } else {
            try {
                f.createNewFile();
                ini = new Wini(f);
            } catch (Exception e) {
                ClientApplication.syslogInfo("Create ini file fail.");
            }
        }
    }

    /**
     * 配置插件参数
     *
     * @param params
     * @throws Exception
     */
    public static void saveConfig(Map<String, Map<String, String>> params) throws Exception {
        ini.clear();
        Set<Map.Entry<String, Map<String, String>>> entries = params.entrySet();
        for (Map.Entry<String, Map<String, String>> item : entries) {
            String section = item.getKey();
            Map<String, String> subParams = item.getValue();
            Set<Map.Entry<String, String>> subEntries = subParams.entrySet();
            for (Map.Entry<String, String> subItem : subEntries) {
                ini.add(section, subItem.getKey(), subItem.getValue());
            }
        }
        ini.store();
    }

    /**
     * 读取插件参数
     *
     * @return
     */
    public static Map<String, Map<String, String>> readConfig() {
        Map<String, Map<String, String>> results = new HashMap<>();
        Set<Map.Entry<String, Profile.Section>> entries = ini.entrySet();
        String key;
        String subkey;
        for (Map.Entry<String, Profile.Section> item : entries) {
            key = item.getKey();
            Profile.Section section = item.getValue();
            Set<String> subKeys = section.keySet();
            Iterator<String> iterator = subKeys.iterator();
            Map<String, String> items = new HashMap<>();
            while (iterator.hasNext()) {
                subkey = iterator.next();
                items.put(subkey, section.get(subkey));
            }
            results.put(key, items);
        }
        return results;
    }

    /**
     * 获取log对象并动态设置日志级别
     *
     * @param clazz
     * @return
     */
    public static Logger getLogger(Class<?> clazz) {
        Logger logger = Logger.getLogger(clazz);
        logger.setLevel(Level.toLevel(ClientApplication.commandCli.getArg("logLevel", "ERROR")));
        return logger;
    }

    /**
     * 系统日志输出，任何情况下都打印
     *
     * @param info
     */
    public static void syslogInfo(String info) {
        System.out.println(info);
    }

    public static AsymmetricEncryption.KeyStringPair getKeyStringPair() {
        return keyStringPair;
    }

    public static void setKeyStringPair(AsymmetricEncryption.KeyStringPair keyStringPair) {
        ClientApplication.keyStringPair = keyStringPair;
    }

    public static String getServerPublicKey() {
        return serverPublicKey;
    }

    public static void setServerPublicKey(String serverPublicKey) {
        ClientApplication.serverPublicKey = serverPublicKey;
    }

    /**
     * 获取当前数据版本
     *
     * @return
     */
    public static long getDataVersion() {
        if (dataVersion >= Long.MAX_VALUE) {
            dataVersion = 0;
        }
        dataVersion++;
        return dataVersion;
    }

    /**
     * 注册时负载均衡
     * @return
     */
    public static NetworkEntity loadBlance(){
        NetworkEntity ne = null;
        List<RegistyUri> registyUris = new ArrayList<>();
        for(String registryHost : registryHosts) {
            RegistyUri registyUri1 = new RegistyUri(registryHost, 0);
            registyUris.add(registyUri1);
        }
        CAlgorithmType cat;
        switch (lbType){
            case 2:
                cat = CAlgorithmType.CYCLE;
                break;
            case 3:
                cat = CAlgorithmType.RANDOM;
                break;
            case 4:
                cat = CAlgorithmType.WEIGHT;
                break;
            case 1:
            default:
                cat = CAlgorithmType.AUTO;
        }
        IotClientLoadBlance iotClientLoadBlance = IotClientLoadBlance.getInstance(registyUris, AlgorithmType.AUTO, cat);
        String serverURI = iotClientLoadBlance.blanceHttp(DEVICE_ID);
        String[] splits = serverURI.split("\\:");
        if(splits.length > 1){
            ne = new NetworkEntity(splits[0], Integer.parseInt(splits[1]));
        }
        return ne;
    }
}
